using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace A4G.Core
{
	public class AlignmentMatrix
	{
		protected AlignmentMatrix(string sequence1, string sequence2, Rectangle sequenceRectangle, IGapMethodProvider gapMethodProvider, ScoringMatrix scoringMatrix, AlignmentScore topLeftScore, bool calculate)
		{
			_sequence1 = sequence1;
			_sequence2 = sequence2;
			_sequenceRectangle = sequenceRectangle;
			_gapMethodProvider = gapMethodProvider;
			_scoringMatrix = scoringMatrix;

			if (calculate)
			{
				List<List<AlignmentScore>> scores = new List<List<AlignmentScore>>();

				int x = 0;
				int y = 0;

				for (x = 0; x <= SequenceRectangle.DiffX + 1; x++)
				{
					List<AlignmentScore> row = new List<AlignmentScore>(SequenceRectangle.DiffY + 1);
					scores.Add(row);
				}

				int stepX = (SequenceRectangle.X1 <= SequenceRectangle.X2) ? 1 : -1;
				int stepY = (SequenceRectangle.Y1 <= SequenceRectangle.Y2) ? 1 : -1;

				int end_i = SequenceRectangle.X2 + stepX;
				int end_j = SequenceRectangle.Y2 + stepY;

				scores[0].Add(topLeftScore);

				y = 0;
				for (int j = sequenceRectangle.Y1; j != end_j; j += stepY, y++)
				{
					Coordinate sequenceCoordinate = new Coordinate(sequenceRectangle.X1 - stepX, j);
					AlignmentScore score = scores[0][y];
					score.FirstSequenceGapCount = 0;
					score = AlignmentScore.CalculateScore(
						score,
						GapMethodProvider,
						this.ScoringMatrix,
						AlignmentScore.gapFirstSequence,
						sequenceCoordinate,
						Sequence1,
						Sequence2);
					score.FirstSequenceGapCount = 0;
					scores[0].Add(score);
				}

				x = 1;
				for (int i = sequenceRectangle.X1; i != end_i; i += stepX, x++)
				{
					Coordinate sequenceCoordinate = new Coordinate(i, sequenceRectangle.Y1 - stepY);
					AlignmentScore score = scores[x - 1][0];
					score.SecondSequenceGapCount = 0;
					score = AlignmentScore.CalculateScore(
						score,
						GapMethodProvider,
						this.ScoringMatrix,
						AlignmentScore.gapSecondSequence,
						sequenceCoordinate,
						Sequence1,
						Sequence2);
					score.SecondSequenceGapCount = 0;
					scores[x].Add(score);

					y = 1;
					for (int j = sequenceRectangle.Y1; j != end_j; j += stepY, y++)
					{
						score = AlignmentScore.CalculateScore(
							scores[x - 1][y - 1],
							scores[x][y - 1],
							scores[x - 1][y],
							GapMethodProvider,
							this.ScoringMatrix,
							new Coordinate(i,j),
							Sequence1,
							Sequence2);
						scores[x].Add(score);
					}
				}

				_scores = new AlignmentScore[scores.Count][];
				for (x = 0; x < scores.Count; x++)
				{
					_scores[x] = scores[x].ToArray();
				}
			}
		}

		public AlignmentMatrix(string sequence1, string sequence2, Rectangle sequenceRectangle, IGapMethodProvider gapMethodProvider, ScoringMatrix scoringMatrix)
			: this(sequence1, sequence2, sequenceRectangle, gapMethodProvider, scoringMatrix, new AlignmentScore())
		{
		}

		public AlignmentMatrix(string sequence1, string sequence2, Rectangle sequenceRectangle, IGapMethodProvider gapMethodProvider, ScoringMatrix scoringMatrix, AlignmentScore topLeftScore)
			: this(sequence1, sequence2, sequenceRectangle, gapMethodProvider, scoringMatrix, topLeftScore, true)
		{
		}

		public AlignmentMatrix(AlignmentScore[] scores, string sequence1, string sequence2, Rectangle sequenceRectangle, IGapMethodProvider gapMethodProvider, ScoringMatrix scoringMatrix)
			: this(sequence1, sequence2, sequenceRectangle, gapMethodProvider, scoringMatrix, new AlignmentScore(), false)
		{
			_scores = new AlignmentScore[1][];
			_scores[0] = scores;
		}

		private readonly string _sequence1;
		public string Sequence1
		{
			get { return _sequence1; }
		}

		private readonly string _sequence2;
		public string Sequence2
		{
			get { return _sequence2; }
		}

		private readonly Rectangle _sequenceRectangle;
		public Rectangle SequenceRectangle
		{
			get { return _sequenceRectangle; }
		}

		public Coordinate BottomRightMatrixCoordinate
		{
			get { return new Coordinate(Scores.Length - 1, Scores[0].Length - 1); }
		}

		private readonly ScoringMatrix _scoringMatrix;
		public ScoringMatrix ScoringMatrix
		{
			get { return _scoringMatrix; }
		}

		private IGapMethodProvider _gapMethodProvider;
		public IGapMethodProvider GapMethodProvider
		{
			get { return _gapMethodProvider; }
		}

		private readonly AlignmentScore[][] _scores;
		public AlignmentScore[][] Scores
		{
			get { return _scores; }
		}

		public AlignmentScore this[int i, int j]
		{
			set
			{
				Scores[i][j] = value;
			}
			get
			{
				return Scores[i][j];
			}
		}

		public int Rows
		{
			get
			{
				return Scores.Length;
			}
		}

		public int Columns
		{
			get
			{
				return Scores[0].Length;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="scoreCoordinateToTraceback">The coordinate within the matrix where to start the traceback.</param>
		/// <param name="sequenceCoordinate">The sequence coordinate that corresponds with the scoreCoordinateToTraceback.</param>
		/// <param name="path">The AlignmentPath</param>
		public void Traceback(ref Coordinate sequenceCoordinate, ref List<AlignmentScore> path)
		{
			int stepX = (SequenceRectangle.X1 <= SequenceRectangle.X2) ? 1 : -1;
			int stepY = (SequenceRectangle.Y1 <= SequenceRectangle.Y2) ? 1 : -1;

			Coordinate coord = sequenceCoordinate;
			Coordinate scoreCoordinate = GetScoreCoordinate(coord);
			while ((scoreCoordinate.X > 0) || (scoreCoordinate.Y > 0))
			{
				if ((scoreCoordinate.X < 0) || (scoreCoordinate.Y < 0))
				{
					break;
				}

				AlignmentScore score = Scores[scoreCoordinate.X][scoreCoordinate.Y];
				path.Add(score);
				sequenceCoordinate = coord;

				if (score.Pointer == TracebackPointers.None)
				{
					break;
				}

				switch (score.Pointer)
				{
					case TracebackPointers.None:
						break;

					case TracebackPointers.Match:
					case TracebackPointers.Mismatch:
						scoreCoordinate--;
						coord.X -= stepX;
						coord.Y -= stepY;
						break;

					case TracebackPointers.GapInFirstSequence:
						scoreCoordinate.Y--;
						coord.Y -= stepY;
						break;

					case TracebackPointers.GapInSecondSequence:
						scoreCoordinate.X--;
						coord.X -= stepX;
						break;

					default:
						break;
				}
			}
		}

		public void ReverseTraceback(
			ref AlignmentScore lastScore,
			ref Coordinate sequenceCoordinate,
			ref List<AlignmentScore> path)
		{
			int stepX = (SequenceRectangle.X1 <= SequenceRectangle.X2) ? 1 : -1;
			int stepY = (SequenceRectangle.Y1 <= SequenceRectangle.Y2) ? 1 : -1;

			Coordinate scoreCoordinate = sequenceCoordinate - SequenceRectangle.Coordinate1;

			int i = scoreCoordinate.X;
			int j = scoreCoordinate.Y;
			while ((i >= 0) && (i < Scores.Length) &&
					(j >= 0) && (j < Scores[i].Length) &&
					(Scores[i][j].Pointer != TracebackPointers.None))
			{
				Coordinate tmp = sequenceCoordinate;

				int index = AlignmentScore.diagonal;
				switch (Scores[i][j].Pointer)
				{
					case TracebackPointers.None:
						break;

					case TracebackPointers.Match:
					case TracebackPointers.Mismatch:
						i++;
						j++;
						tmp.X += stepX;
						tmp.Y += stepY;
						index = AlignmentScore.diagonal;
						break;

					case TracebackPointers.GapInFirstSequence:
						j++;
						tmp.Y += stepY;
						index = AlignmentScore.gapFirstSequence;
						break;

					case TracebackPointers.GapInSecondSequence:
						i++;
						tmp.X += stepX;
						index = AlignmentScore.gapSecondSequence;
						break;

					default:
						break;
				}

				if ((i >= 0) && (i < Scores.Length) &&
					(j >= 0) && (j < Scores[i].Length))
				{
					sequenceCoordinate = tmp;
					lastScore = AlignmentScore.CalculateScore(
						lastScore,
						GapMethodProvider,
						ScoringMatrix,
						index,
						sequenceCoordinate,
						Sequence1,
						Sequence2);

					path.Add(lastScore);
				}
			}
		}

		private Coordinate GetScoreCoordinate(Coordinate sequenceCoordinate)
		{
			return BottomRightMatrixCoordinate -
				(SequenceRectangle.Coordinate2 - sequenceCoordinate);
		}
	}
}
